package org.dreamwork.jasmine2.restful;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.misc.Base64;
import org.dreamwork.secure.SecureUtil;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.*;
import java.net.URLDecoder;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: seth.yang
 * Date: 14-11-18
 * Time: 上午11:08
 */
@WebServlet (urlPatterns = "/api/*", loadOnStartup = 1)
public class RestfulAPIServlet extends HttpServlet {
    private static final Logger logger = LoggerFactory.getLogger (RestfulAPIServlet.class);

    public static final String KEY_API_CLIENT = "appId";
    public static final String KEY_API_ENCRYPTED = "encrypted";
    public static final String KEY_API_ENCRYPTED_BODY = "encrypted_body";
    public static final String JSON_CONTENT_TYPE = "application/json;charset=utf-8";
    public static final String ENC = "utf-8";

    private SecureUtil secureUtil;
    private TypeToken<Map<String, Object>> token = new TypeToken<Map<String, Object>> () {};
    private Map<String, APIDefinition> map;

    @Override
    public void init (ServletConfig config) throws ServletException {
        super.init (config);
        ServletContext application = getServletContext ();
        Object obj = application.getAttribute (RestfulAPIManager.KEY_API_MAPPING);
        if (obj == null) {
            loadAPIConfig (config);
        }

        if (RestfulAPIContext.secureContext != null)
            secureUtil = new SecureUtil (RestfulAPIContext.secureContext);
    }

    @Override
    protected void service (HttpServletRequest request, HttpServletResponse response) throws IOException {
        String method = request.getMethod ();
        String pathInfo = request.getPathInfo ();

        if (logger.isTraceEnabled ()) {
            logger.trace ("Receiving a request: ");
            logger.trace ("\tmethod  : {}", method);
            logger.trace ("\tpathInfo: {}", pathInfo);
        }

        if (pathInfo == null || pathInfo.equals ("/")) {
            // 访问了 api 的根目录，视为请求 api 文档
            if (logger.isTraceEnabled ())
                logger.trace ("fetching api document");
            if ("post".equalsIgnoreCase (method))
                showDescription (response, "json");
            else if ("get".equalsIgnoreCase (method)) {
                String queryString = request.getQueryString ();
                if ("json".equals (queryString))
                    showDescription (response, "json");
                else
                    showDescription (response, "html");
            }
            return;
        }

        if (pathInfo.charAt (0) == '/')
            pathInfo = pathInfo.substring (1);

        int pos = pathInfo.indexOf ('/');
        if (pos < 0) { // api description
            response.setStatus (HttpServletResponse.SC_NOT_FOUND);
        } else { // api invoke
            String apiPattern = pathInfo.substring (0, pos),
                   methodPattern = pathInfo.substring (pos + 1);
            if (logger.isTraceEnabled ()) {
                logger.trace ("api    = {}", apiPattern);
                logger.trace ("method = {}", method);
            }
            RestfulAPIContext context = new RestfulAPIContext ();
            try {
                int code = parseContext (apiPattern, methodPattern, request, context);
                if (code != HttpServletResponse.SC_OK) {
                    response.setStatus (code);
                } else {
                    if (StringUtil.isEmpty (context.client)) {
                        // 没有提供 app-id
                        if (RestfulAPIContext.appIdRequired != null && RestfulAPIContext.appIdRequired) {
                            // 配置要求必须有 app-id
                            response.setStatus (HttpServletResponse.SC_FORBIDDEN);
                            return;
                        }
                    }
                    dispatchAPI (apiPattern, methodPattern, context, request, response);
                }
            } finally {
                // clean up
                context.dispose ();
            }
        }
    }

    /**
     * 检查API映射是否正确
     * @param map 预先加载的所有 API 映射集
     * @param apiPattern API 映射模式
     * @throws RestfulAPIException 未包含
     */
    private void checkApiMapping (Map<String, APIDefinition> map, String apiPattern) throws RestfulAPIException {
        if (!map.containsKey (apiPattern)) {
            logger.warn ("Can't find pattern: " + apiPattern);
            throw new RestfulAPIException (RestfulAPIErrorMessage.API_NOT_FOUND);
        }
    }

    /**
     * 检查API方法是否可调用.
     *
     * 1. 检查方法映射是否正确
     * 2. 检查所有必须的Context Parameter是否有参数
     *
     * @param context 当前的 Restful API 上下文
     * @param def API 定义
     * @param md 方法定义
     * @param apiPattern API 映射
     * @param methodPattern 方法映射
     * @throws RestfulAPIException HTTP 方法不支持或必去的Context Parameter缺失
     */
    private void checkMethod (RestfulAPIContext context, APIDefinition def, MethodDefinition md, String apiPattern, String methodPattern) throws RestfulAPIException {
        MethodType method = md.getType ();
        if (!def.isMethodSupported (methodPattern, method)) {
            logger.warn ("pattern " + apiPattern + '/' + methodPattern + " dose not support " + method + " request.");
            throw new RestfulAPIException (RestfulAPIErrorMessage.METHOD_NOT_SUPPORTED);
        }

        for (ContextParameterDefinition p : md.getContextParameters ()) {
            if (p.isRequired () && !context.contains (p.getName ())) {
                logger.warn (
                        "missing required context parameter[{}] for pattern[{}/{}] on method[{}]",
                        p.getName (), apiPattern, methodPattern, method
                );
                throw new RestfulAPIException (RestfulAPIErrorMessage.MISSING_REQUIRED_CONTEXT_PARAMETER);
            }
        }
    }

    private void dispatchAPI (String apiPattern, String methodPattern, RestfulAPIContext context, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (logger.isTraceEnabled ())
            logger.trace ("dispatching api ");
//        ServletContext application = getServletContext ();
//        Map<String, APIDefinition> map = (Map<String, APIDefinition>) application.getAttribute (RestfulAPIManager.KEY_API_MAPPING);
        APIDefinition def = map.get (apiPattern);
        MethodDefinition md = def.getMethod (methodPattern);

        if (md == null) {
            response.setStatus (HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        String httpMethod = request.getMethod ();
        MethodType mt = MethodType.parse (httpMethod);
        if (mt != md.type) {
            response.setStatus (HttpServletResponse.SC_METHOD_NOT_ALLOWED);
            return;
        }

        Class<?> type = def.getType ();
        try {
            checkApiMapping (map, apiPattern);
            checkMethod (context, def, md, apiPattern, methodPattern);

            // check force encrypt
            if (def.forceEncrypt || md.forceEncrypt) {
                if (!context.encrypted) {
                    showError (RestfulAPIErrorMessage.ENCRYPT_FORCED, response);
                    return;
                }
            }

            Object api = type.newInstance (), result;
            if (logger.isTraceEnabled ()) {
                logger.trace ("RestfulAPI instance created as {}", api);
                logger.trace ("Invoking api with context: {}", context);
            }

            injectContextParameter (api, md.getContextParameters (), context, request, response);

            // add by seth.yang on 2014-12-12
            // for patching execution before restful-api invoked.
            beforeExecute (def, md, api);

            Gson g = GsonHelper.getGson (true, true);

            if (md.parameters.size () == 0) {
                // empty parameter method
                Method m = type.getMethod (md.name);
                result = m.invoke (api);
            } else if (md.parameters.size () == 1) {
                // single parameter method
                Object parameter = getSingleParameter (md, request);
                Method m = type.getMethod (md.name, md.parameters.get (0).getType ());
                result = m.invoke (api, parameter);
            } else {
                // normal method.
                result = execute (context, api, md, request, type, g);
            }

            writeResult (result, response, md.forceJson);
        } catch (RestfulAPIException ex) {
            showError (ex.buildErrorMessage (), response);
        } catch (InvocationTargetException ex) {
            logger.error (ex.getMessage (), ex);
            Throwable t = ex.getTargetException ();
            if (t instanceof RestfulAPIException) {
                RestfulAPIException re = (RestfulAPIException) t;
                showError (re.buildErrorMessage (), response);
            } else {
                RestfulAPIErrorMessage em = new RestfulAPIErrorMessage (RestfulAPIErrorMessage.INTERNAL_SERVER_ERROR_CODE, t.getMessage ());
                showError (em, response);
            }
        } catch (Exception ex) {
            logger.error (ex.getMessage (), ex);
            showError (new RestfulAPIErrorMessage (RestfulAPIErrorMessage.INTERNAL_SERVER_ERROR_CODE, ex.getMessage ()), response);
        }
    }

    private void writeResult (Object result, HttpServletResponse response, boolean forceJson) throws Exception {
        RestfulAPIContext context = RestfulAPIContext.getCurrentContext ();
        if (result == null) {
            if (logger.isDebugEnabled ())
                logger.debug ("The API has no return value.");
            showError (RestfulAPIErrorMessage.OK, response);
        } else {
            Gson g = GsonHelper.getGson (true, true);
            String content;
            response.setContentType (JSON_CONTENT_TYPE);
            if (context.encrypted || forceJson) {
                Map<String, Object> map = new HashMap<> ();
                map.put ("code", RestfulAPIErrorMessage.OK.getCode ());
                if (context.encrypted) {
                    content = encrypt (g.toJson (result), context);
                    map.put (KEY_API_ENCRYPTED_BODY, content);
                } else {
                    map.put ("response", result);
                }

                content = g.toJson (map);
            } else {
                content = g.toJson (result);
            }
            byte[] buff = content.getBytes (ENC);
            response.setContentLength (buff.length);
            response.getOutputStream ().write (buff);
        }
    }

    private void showError (RestfulAPIErrorMessage message, HttpServletResponse response) throws IOException {
        response.setContentType (JSON_CONTENT_TYPE);
        String content = new Gson ().toJson (message);
        response.getWriter ().write (content);
    }

    private void showDescription (HttpServletResponse response, String type) throws IOException {
        if ("html".equalsIgnoreCase (type)) {
            response.setContentType ("text/html;charset=utf-8");
            APIInfoPage page = new APIInfoPage ();
            page.setApplication (getServletContext ());
            page.render (response);
        } else {
            response.setContentType ("application/json;charset=utf-8");
            ServletContext application = getServletContext ();
            Gson g = new GsonBuilder ().excludeFieldsWithoutExposeAnnotation ().create ();
            Map<String, Object> map = new HashMap<> ();
            map.put ("api-mapping", application.getAttribute (RestfulAPIManager.KEY_API_MAPPING));
            if (RestfulAPIContext.secureContext != null) {
                map.put ("secure-context", RestfulAPIContext.secureContext);
            }
            map.put ("title", RestfulAPIContext.getTitle());
            if (!StringUtil.isEmpty (RestfulAPIContext.getDescription()))
                map.put ("description", RestfulAPIContext.getDescription().trim ());
            String content = g.toJson (map);
            response.getWriter ().write (content);
        }
    }

    @SuppressWarnings ("unchecked")
    private int parseContext (String api, String method, HttpServletRequest request, RestfulAPIContext context) throws IOException {
        context.mapping = request.getServletPath ();
        context.application = getServletContext ();

//        Map<String, APIDefinition> map = (Map<String, APIDefinition>) getServletContext ().getAttribute (RestfulAPIManager.KEY_API_MAPPING);
        APIDefinition ad = map.get (api);
        if (ad == null) {
            return HttpServletResponse.SC_NOT_FOUND;
        }
        MethodDefinition md = ad.getMethod (method);
        if (md == null) {
            return HttpServletResponse.SC_NOT_FOUND;
        }

        String queryString = request.getQueryString ();
        if (!StringUtil.isEmpty (queryString)) {
            queryString = queryString.trim ();
            if (queryString.charAt (0) == '?')
                queryString = queryString.substring (1);

            String[] a = queryString.split ("&");
            for (String p : a) {
                String[] pp = p.split ("=");
                if (pp.length < 1) continue;
                if (StringUtil.isEmpty (pp[0])) continue;

                String name = pp[0].trim ();
                if (pp.length > 1) {
                    String value = URLDecoder.decode (pp[1], ENC);
                    if (name.equals (KEY_API_CLIENT) && RestfulAPIContext.location == AppIdLocation.queryString) {
                        context.client = value;
                    } else if (name.equals (KEY_API_ENCRYPTED)) {
                        context.encrypted = "true".equalsIgnoreCase (value.trim ());
                    } else if (isContextParameter (ad, md, name)) {
                        context.set (name, value);
                    }
                }
            }
        }

        if (RestfulAPIContext.location == AppIdLocation.header) {
            String appId = request.getHeader ("appId");
            if (StringUtil.isEmpty (appId) && RestfulAPIContext.appIdRequired) {
                return HttpServletResponse.SC_UNAUTHORIZED;
            }
            context.client = appId;
        } else if (RestfulAPIContext.location == AppIdLocation.queryString) {
            if (RestfulAPIContext.appIdRequired && StringUtil.isEmpty (context.client)) {
                return HttpServletResponse.SC_UNAUTHORIZED;
            }
        }

        return HttpServletResponse.SC_OK;
    }

    /**
     * 注入环境参数
     * @param bean    准备执行的对象
     * @param c       环境参数配置
     * @param context restful 环境
     * @throws Exception if any
     */
    private void injectContextParameter (Object bean, Collection<ContextParameterDefinition> c, RestfulAPIContext context, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Class<?> type = bean.getClass ();
        for (ContextParameterDefinition cpd : c) {
            String name = cpd.getName ();
            if (logger.isTraceEnabled ()) {
                logger.trace ("trying to inject context parameter: {}", name);
            }
            Field field = org.dreamwork.util.ReferenceUtil.findField (type, name);
            if (field != null) {
                if (!field.isAccessible ()) {
                    field.setAccessible (true);
                }
                Class<?> pt = field.getType ();
                if (pt == ServletContext.class) {
                    field.set (bean, getServletContext ());
                } else if (pt.isAssignableFrom (HttpServletResponse.class)) {
                    field.set (bean, response);
                } else if (pt.isAssignableFrom (HttpServletRequest.class)) {
                    field.set (bean, request);
                } else {
                    String cp = context.get (name);
                    Object value = cast (pt, cp);
                    if (value != null)
                        field.set (bean, value);
                }
            } else if (cpd.isRequired ()) {
                throw new RestfulAPIException ("Can't inject context parameter: " + name, 0x0005);
            }
        }
    }

    private Object getSingleParameter (MethodDefinition md, HttpServletRequest request) throws Exception {
        RestfulAPIContext context = RestfulAPIContext.getCurrentContext ();
        MethodType mt = md.getType ();
        APIParameterDefinition pd = md.parameters.get (0);
        switch (mt) {
            case GET: {
                if (Map.class.isAssignableFrom (pd.getType ())) {
                    return getSimpleParameterAsMap (request, md, context, pd);
                }

                String value = request.getParameter (pd.getName ());
                if (value == null) {
                    // 参数可空
                    if (pd.isNullable ())
                        return null;
                    throw new RestfulAPIException (
                            "Missing API Parameter: " + pd.getName (),
                            RestfulAPIErrorMessage.MISSING_API_PARAMETER_CODE
                    );
                }
                if (context.encrypted) {
                    value = decrypt (value, context);
                }
                return cast (pd.getType (), value);
            }
            case POST:
            case PUT:
            case DELETE: {
                // queryString 传参的优先级最高
                String query = request.getQueryString ();
                if (!StringUtil.isEmpty (query)) {
                    query = query.trim ();
                    if (query.charAt (0) == '?') {
                        query = query.substring (1);
                    }
                    if (logger.isTraceEnabled ()) {
                        logger.trace ("query string = {}", query);
                    }

                    Object value = findSingleObject (pd, query, context);
                    if (value != null) {
                        if (logger.isTraceEnabled ()) {
                            logger.trace ("find value from query string: {}", value);
                        }
                        return value;
                    }
                }
                if (logger.isTraceEnabled ()) {
                    logger.trace ("can't find value from query string, find parameter from body");
                }
                String body = readPostBody (request);
                if (StringUtil.isEmpty (body)) {
                    if (logger.isTraceEnabled ()) {
                        logger.warn ("request body is empty !");
                    }
                    if (pd.isNullable ()) {
                        // 允许空值
                        return null;
                    }
                    // body 无内容
                    throw new RestfulAPIException (RestfulAPIErrorMessage.MISSING_API_PARAMETER);
                }
                body = body.trim ();
                String contentType = request.getContentType ();
                if (logger.isTraceEnabled ()) {
                    logger.trace ("request body = {}", body);
                    logger.trace ("content type = {}", contentType);
                }

                if (context.encrypted) {
                    if (logger.isTraceEnabled ()) {
                        logger.trace ("the body is encrypted. trying to decrypt it");
                    }
                    body = decrypt (body, context);
                    if (logger.isTraceEnabled ()) {
                        logger.trace ("decrypted body = {}", body);
                    }
                }

                if (contentType == null || "application/x-www-form-urlencoded".equals (contentType)) {
                    // form 格式的参数, 已经解密
                    Object value = findSingleObject (pd, body, null);
                    if (value != null) {
                        return value;
                    }
                    if (pd.isNullable ()) {
                        // 允许空值
                        return null;
                    }
                    throw new RestfulAPIException (RestfulAPIErrorMessage.MISSING_API_PARAMETER);
                } else if (contentType.startsWith ("application/json")) {
                    try {
                        if (logger.isTraceEnabled ()) {
                            logger.trace ("trying to parse body as simple value");
                        }
                        // 当作简单类型处理
                        return cast (pd.getType (), body);
                    } catch (Exception ex) {
                        if (logger.isTraceEnabled ()) {
                            logger.warn (ex.getMessage (), ex);
                        }
                    }

                    if (logger.isTraceEnabled ()) {
                        logger.trace ("parse body as complex content");
                    }
                    Map<String, Object> map = new Gson ().fromJson (body, token.getType ());
                    if (map != null) {
                        Object o = map.get (pd.getName ());
                        if (o != null) {
                            if (logger.isTraceEnabled ()) {
                                logger.trace ("find value from body: {}", o);
                            }
                            return cast (pd.getType (), String.valueOf (o));
                        }
                    }
                    if (pd.isNullable ()) {
                        return null;
                    }
                    throw new RestfulAPIException (RestfulAPIErrorMessage.MISSING_API_PARAMETER);
                }
            }
            default:
                throw new RestfulAPIException (RestfulAPIErrorMessage.METHOD_NOT_SUPPORTED);
        }
    }

    @SuppressWarnings ("unchecked")
    private Map<String, Object> getSimpleParameterAsMap (HttpServletRequest request, MethodDefinition md, RestfulAPIContext context, APIParameterDefinition pd) throws Exception {
        Map map = createInstance ((Class<? extends Map<?, ?>>) pd.getType ());
        if (context.encrypted) {
            String encryptedParameter = request.getParameter (KEY_API_ENCRYPTED_BODY);
            if (StringUtil.isEmpty (encryptedParameter))
                throw new RestfulAPIException ("Missing Encrypted Body", RestfulAPIErrorMessage.MISSING_API_PARAMETER_CODE);

            String content = decrypt (encryptedParameter, context);
            String[] tmp = content.split ("&");
            for (String part : tmp) {
                String[] a = part.split ("=");
                if (a.length <= 1) {
                    logger.warn ("empty parameter, ignore it");
                    continue;
                }
                String name = a[0], value = a[1];
                if (map.containsKey (name)) {
                    Object o = map.get (name);
                    if (o.getClass () == String.class) {
                        List<String> list = new ArrayList<> ();
                        list.add ((String) o);
                        list.add (value);
                        map.put (name, list);
                    } else {
                        ((List<String>) o).add (value);
                    }
                } else {
                    map.put (name, value);
                }
            }
        } else {
            for (Enumeration<String> e = request.getParameterNames (); e.hasMoreElements ();) {
                String name = e.nextElement ();
                if (md.isContextParameter (name)) continue;

                String[] values = request.getParameterValues (name);
                if (values.length > 1)
                    map.put (name, values);
                else if (values.length == 1)
                    map.put (name, values [0]);
            }
        }
        return map;
    }

    private Object getParameter (APIParameterDefinition pd, HttpServletRequest request) throws RestfulAPIException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
        MethodType mt = MethodType.parse (request.getMethod ());
        if (mt == null)
            throw new RestfulAPIException (RestfulAPIErrorMessage.METHOD_NOT_SUPPORTED);

        if (mt == MethodType.GET) {
            String value = request.getParameter (pd.getName ());
            if (value == null) {
                if (pd.isNullable ())
                    return null;
                throw new RestfulAPIException ("Missing API Parameter: " + pd.getName (), RestfulAPIErrorMessage.MISSING_API_PARAMETER_CODE);
            }
            return cast (pd.getType (), value);
        } else if (mt == MethodType.POST) {
            String body = readPostBody (request);
            return cast (pd.getType (), body);
        } else {
            throw new RestfulAPIException (RestfulAPIErrorMessage.METHOD_NOT_SUPPORTED);
        }
    }

    private String readPostBody (HttpServletRequest request) throws IOException {
        byte[] buff = new byte[512];
        int length;
        ByteArrayOutputStream baos = new ByteArrayOutputStream ();
        InputStream in = request.getInputStream ();
        while ((length = in.read (buff)) != -1)
            baos.write (buff, 0, length);

        return baos.toString (ENC);
    }

    private Object cast (Class<?> type, String value) throws IllegalAccessException, InvocationTargetException, RestfulAPIException {
        if (type == String.class) return value;
        if (type.isPrimitive () || Number.class.isAssignableFrom (type)) {
            if (logger.isDebugEnabled ())
                logger.debug ("type is primitive, invoke gson's cast!");
            return new Gson ().fromJson (value, type);
        }

        try {
            if (logger.isDebugEnabled ())
                logger.debug ("trying to find static method parse(String) ...");
            Method method = type.getMethod ("parse", String.class);
            if (method != null) {
                if (logger.isDebugEnabled ())
                    logger.debug ("found static method parse(String), invoke it!");
                return method.invoke (null, value);
            } else {
                if (logger.isDebugEnabled ())
                    logger.debug ("can't find static method parse(String), try another way.");
            }
        } catch (NoSuchMethodException nsme) {
            //
        }

        if (type.isEnum ()) {
            try {
                if (logger.isDebugEnabled ())
                    logger.debug ("type is an enum, try parse it use jdk api: static java.util.Enum.valueOf (Class, String)");
                @SuppressWarnings ("unchecked")
                Class<Enum> ec = (Class<Enum>) type;
                return Enum.valueOf (ec, value);
            } catch (Exception ex) {
                throw new RestfulAPIException (RestfulAPIErrorMessage.PARAMETER_INVALID);
            }
        }

        Gson g = GsonHelper.getGson ();
        return g.fromJson (value, type);
    }

    private Map createInstance (Class<? extends Map> type) throws IllegalAccessException, InstantiationException {
        if (type.isInterface ())
            return new HashMap ();

        return type.newInstance ();
    }

    private String decrypt (String encryptedParameter, RestfulAPIContext context) throws Exception {
        // patch base64 encode
        encryptedParameter = encryptedParameter.replace (' ', '+');
        byte[] buff = Base64.decode (encryptedParameter.getBytes (ENC));
        PrivateKey kek = context.getSecureContext ().getFetcher ().getPrivateKey (context.client);
        buff = secureUtil.decrypt (buff, kek);
        return new String (buff, ENC);
    }

    private String encrypt (String body, RestfulAPIContext context) throws Exception {
        byte[] buff = body.getBytes (ENC);
        PublicKey kek = context.getSecureContext ().getFetcher ().getPublicKey (context.client);
        buff = secureUtil.encrypt (buff, kek);
        return new String (Base64.encode (buff), ENC);
    }

    private void loadAPIConfig (ServletConfig config) throws ServletException {
        ServletContext application = getServletContext ();
        String config_path = config.getInitParameter ("api-config");
        if (logger.isTraceEnabled ()) {
            logger.trace ("config path = {}", config_path);
        }

        if (StringUtil.isEmpty (config_path)) {
            config_path = getServletContext ().getInitParameter ("api-config");
        }

        if (StringUtil.isEmpty (config_path)) {
            config_path = "/WEB-INF/api.xml";
        }

        try (InputStream in = application.getResourceAsStream (config_path)) {
            if (in == null) {
                throw new ServletException ("Can't find api config: " + config_path);
            }

            map = RestfulAPIManager.load (in, application);
            application.setAttribute (RestfulAPIManager.KEY_API_MAPPING, map);
        } catch (Exception ex) {
            throw new ServletException (ex);
        }
    }

    private boolean isContextParameter (APIDefinition api, MethodDefinition md, String name) {
        return RestfulAPIContext.isContextParameter (name)
            || api.isContextParameter (name)
            || md.isContextParameter (name);
    }

    @SuppressWarnings ("unchecked")
    private Map<String, Object> decryptAsMap (String content) throws Exception {
        RestfulAPIContext context = RestfulAPIContext.getCurrentContext ();
        content = decrypt (content, context);
        Map<String, Object> map = new HashMap<> ();
        String[] temp = content.split ("&");
        for (String pair : temp) {
            String[] a = pair.split ("=");
            if (a.length < 2) continue;
            String name = a [0].trim (), value = a [1];
            if (StringUtil.isEmpty (value)) continue;

            if (map.containsKey (name)) {
                Object o = map.get (name);
                Class<?> type = o.getClass ();
                if (List.class.isAssignableFrom (type)) {
                    ((List<String>) o).add (value);
                } else if (type == String.class) {
                    List<String> list = new ArrayList<> ();
                    list.add ((String) o);
                    list.add (value);
                    map.put (name, list);
                }
            } else
                map.put (name, value.trim ());
        }

        return map;
    }

    /**
     * api 真正被调用的前置操作
     * @param def api 定义
     * @param md  方法定义
     * @param api api实现对象
     * @throws RestfulAPIException if any
     */
    private void beforeExecute (APIDefinition def, MethodDefinition md, Object api) throws RestfulAPIException {
        if (!md.skipBeforeExecute && !def.beforeApiExecuteMethods.isEmpty ()) {
            try {
                for (Method method : def.beforeApiExecuteMethods) {
                    Object flag = method.invoke (api);
                    if (flag != null) {
                        Class<?> rt = flag.getClass ();
                        if (rt == boolean.class || rt == Boolean.class) {
                            if (!(Boolean) flag) {
                                throw new RestfulAPIException (RestfulAPIErrorMessage.BEFORE_EXECUTE_FAIL);
                            }
                        }
                    }
                }
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause ();
                while (cause != null) {
                    if (cause instanceof RestfulAPIException) {
                        throw (RestfulAPIException) cause;
                    } else {
                        cause = cause.getCause ();
                    }
                }
                throw new RestfulAPIException (ex.getTargetException (), RestfulAPIErrorMessage.INTERNAL_SERVER_ERROR_CODE);
            } catch (Exception ex) {
                throw new RestfulAPIException (ex, RestfulAPIErrorMessage.INTERNAL_SERVER_ERROR_CODE);
            }
        }
    }

    private Object execute (RestfulAPIContext context, Object api, MethodDefinition md, HttpServletRequest request, Class<?> type, Gson g) throws Exception {
        Class<?>[] pcs = new Class[md.parameters.size ()];
        Object[] pvs = new Object[md.parameters.size ()];
        switch (md.getType ()) {
            case GET:
                if (context.encrypted) {
                    String encrypted_body = request.getParameter (KEY_API_ENCRYPTED_BODY);
                    Map<String, Object> m = decryptAsMap (encrypted_body);

                    for (int i = 0; i < md.parameters.size (); i++) {
                        APIParameterDefinition pd = md.parameters.get (i);
                        pcs[i] = pd.getType ();
                        Object value = m.get (pd.getName ());
                        if (value == null) {
                            if (!pd.isNullable ())
                                throw new RestfulAPIException (pd.getName (), RestfulAPIErrorMessage.MISSING_API_PARAMETER_CODE);
                        } else if (value.getClass () == String.class) {
                            pvs[i] = cast (pd.getType (), (String) value);
                        } else {
                            String temp = g.toJson (value);
                            pvs[i] = cast (pd.getType (), temp);
                        }
                    }
                } else for (int i = 0; i < md.parameters.size (); i++) {
                    APIParameterDefinition pd = md.parameters.get (i);
                    pcs[i] = pd.getType ();
                    pvs[i] = getParameter (pd, request);
                }
                break;
            case POST:
            case DELETE:
            case PUT:
                String body = readPostBody (request);
                if (StringUtil.isEmpty (body)) {
                    throw new RestfulAPIException (RestfulAPIErrorMessage.MISSING_API_PARAMETER);
                }

                if (context.encrypted) {
                    body = decrypt (body, context);
                }

                Map<String, Object> temp = g.fromJson (body, token.getType ());
                if (temp == null || temp.isEmpty ()) {
                    throw new RestfulAPIException (RestfulAPIErrorMessage.MISSING_API_PARAMETER);
                }

                for (int i = 0; i < md.parameters.size (); i++) {
                    APIParameterDefinition pd = md.parameters.get (i);
                    pcs[i] = pd.getType ();
                    Object v = temp.get (pd.getName ());
                    String e = g.toJson (v);
                    pvs[i] = g.fromJson (e, pd.getType ());
                }
                break;
            default:
                throw new RestfulAPIException (RestfulAPIErrorMessage.METHOD_NOT_SUPPORTED);
        }

        Method m = type.getMethod (md.name, pcs);
        return m.invoke (api, pvs);
    }

    private void urlDecode (String text, Map<String, String> map) throws UnsupportedEncodingException {
        if (!StringUtil.isEmpty (text)) {
            String[] temp = text.trim ().split ("&");
            for (String p : temp) {
                if (StringUtil.isEmpty (p)) continue;

                String[] a = p.split ("=");
                String key   = a[0];
                String value = a.length > 1 ? URLDecoder.decode (a[1].trim (), "utf-8") : a[0];
                map.put (key.trim (), value.trim ());
            }
        }
    }

    private Object findSingleObject (APIParameterDefinition pd, String body, RestfulAPIContext context) throws Exception {
        Map<String, String> map = new HashMap<> ();
        urlDecode (body, map);
        String value;
        if (context != null && context.encrypted) {
            value = map.get (KEY_API_ENCRYPTED_BODY);
            if (StringUtil.isEmpty (value)) {
                throw new RestfulAPIException ("missing encrypted parameter", RestfulAPIErrorMessage.MISSING_API_PARAMETER_CODE);
            }
            Map<String, Object> m = decryptAsMap (value);
            Object o = m.get (pd.getName ());
            if (o != null) {
                value = String.valueOf (o);
            }
        } else {
            value = map.get (pd.getName ());
        }

        if (!StringUtil.isEmpty (value)) {
            value = value.trim ();
            if (context != null && context.encrypted) {
                value = decrypt (value, context);
            }
            return cast (pd.getType (), value);
        }

        return null;
    }
}